package com.bckj.fastboot.core.lang.model;

import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

@SuppressWarnings("unchecked")
public record Result<T>(boolean success, T result, Throwable error) {

    private static final Result<?> EMPTY = new Result<>(false, null, null);

    public static <T> Result<T> empty() {
        return (Result<T>) EMPTY;
    }

    public static <T> Result<T> error(Throwable error) {
        return new Result<>(false, null, error);
    }

    public static <T, U> Result<U> wrap(Result<T> result) {
        return new Result<>(result.success(), (U) result.getOrNull(), result.error());
    }

    public Result(boolean success, T result, Throwable error) {
        this.success = success;
        this.result = result;
        this.error = error;
    }

    public boolean isEmpty() {
        return result == null;
    }

    public boolean hasError() {
        return !success || error != null;
    }

    public boolean hasErrorOrEmpty() {
        return hasError() || isEmpty();
    }

    public Result<T> filter(Function<T, Boolean> predicate) {
        Objects.requireNonNull(predicate);
        if (success && predicate.apply(result)) {
            return this;
        } else {
            return error(error);
        }
    }

    public <U> Result<U> map(Function<T, U> mapper) {
        Objects.requireNonNull(mapper);
        if (success) {
            if (isEmpty()) {
                return empty();
            }
            return new Result<>(true, mapper.apply(result), null);
        } else {
            return error(error);
        }
    }

    public <U> Result<U> flatMap(Function<T, Result<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (success) {
            if (isEmpty()) {
                return empty();
            }
            return mapper.apply(result);
        } else {
            return error(error);
        }
    }

    public T getOrNull() {
        return result;
    }

    public T orElse(T other) {
        return isEmpty() ? other : result;
    }

    public T orThrow() {
        if (success) {
            return result;
        } else {
            throw (RuntimeException) error;
        }
    }

    public T orThrow(Function<Throwable, ? extends RuntimeException> errorFunction) {
        if (success) {
            return result;
        } else {
            throw errorFunction.apply(error);
        }
    }

    public T orElseThrow(T other) {
        if (success) {
            return isEmpty() ? other : result;
        } else {
            throw (RuntimeException) error;
        }
    }

    public boolean isPresent() {
        return success && !isEmpty();
    }

    public void ifPresent(Consumer<T> consumer) {
        if (isPresent()) {
            consumer.accept(result);
        }
    }

    public static <T> Result<T> success(T result) {
        return new Result<>(true, result, null);
    }

    public static <T> Result<T> failure(Throwable error) {
        return new Result<>(false, null, error);
    }

    public static <T> Result<T> of(T result, Throwable error) {
        return new Result<>(error == null, result, error);
    }

    public static <T> Result<T> of(T result) {
        return new Result<>(true, result, null);
    }
}
